package com.ddf.springdata.jpa.dao.impl;

import com.ddf.springdata.jpa.dao.JpaBaseDao;
import com.ddf.springdata.jpa.entity.BaseDomain;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义JPA的通用查询方法，该对象不提供直接使用，需要对象创建自己的Repository借口来继承该实现类的借口类
 * @see JpaBaseDao
 * @see com.ddf.springdata.jpa.config.MainConfiguration
 * @author DDf on 2018/12/11
 */
public class JapBaseDaoImpl<T extends BaseDomain, S> extends SimpleJpaRepository<T, S> implements JpaBaseDao<T, S> {

	/**
	 * JPA操作数据的原生对象
	 */
	private EntityManager entityManager;

	/**
	 * 包含实体类对象的信息，如类名
	 */
	private JpaEntityInformation entityInformation;

	/**
	 * 缓存每次调用自定义查询的实体对象的属性信息，重复调用不会重复缓存
	 */
	private Map<String, Map<String, Field>> classMap = new ConcurrentHashMap<>();

	/**
	 * 获得JPA操作数据的原生对象和当前的被操作的实体对象信息
	 * @param entityInformation
	 * @param entityManager
	 */
	JapBaseDaoImpl(JpaEntityInformation entityInformation,
	               EntityManager entityManager) {
		super(entityInformation, entityManager);
		// Keep the EntityManager around to used from the newly introduced methods.
		this.entityManager = entityManager;
		this.entityInformation = entityInformation;
	}


	/**
	 * 单表根据字段值和value匹配查询返回一个对象
	 *
	 * @param propertiesMap field-value键值对
	 * @return
	 */
	@Override
	public T findOneByProperties(Map<String, String> propertiesMap) {
		return findOneByProperties(propertiesMap, false);
	}


	/**
	 * @param propertiesMap field-value键值对
	 * @param isRemoved     是否包含removed=1的条件
	 * @return
	 */
	@Override
	public T findOneByProperties(Map<String, String> propertiesMap, boolean isRemoved) {
		StringBuffer sbl = buildSql(propertiesMap, isRemoved);
		try {
			return (T) entityManager.createQuery(sbl.toString(), entityInformation.getJavaType()).getSingleResult();
		} catch (EmptyResultDataAccessException | NoResultException e) {
			return null;
		}
	}

	/**
	 * 构造hql
	 * @param propertiesMap  属性值
	 * @param isRemoved 是否加入removed=1的条件
	 * @return
	 */
	private StringBuffer buildSql(Map<String, String> propertiesMap, boolean isRemoved) {
		StringBuffer sbl = new StringBuffer();
		sbl.append("FROM ").append(entityInformation.getJavaType().getSimpleName()).append(" ");
		if (isRemoved) {
			sbl.append(" WHERE removed = 1");
		} else {
			sbl.append(" WHERE removed = 0");
		}
		if (propertiesMap != null && !propertiesMap.isEmpty()) {
			Map<String, Field> fieldMap = getCachedFieldMap();
			propertiesMap.forEach((k, v) -> {
				if (!fieldMap.containsKey(k)) {
					throw new RuntimeException("条件字段与对象属性不匹配，请检查重试");
				}
				Field field = fieldMap.get(k);
				sbl.append(" AND ").append(k).append("= ");
				if (field.getType().getName().equals(String.class.getName())) {
					sbl.append("'").append(v).append("' ");
				} else {
					sbl.append(v);
				}
			});
		}
		return sbl;
	}

	/**
	 * 单表根据字段值和value匹配查询返回对象列表
	 *
	 * @param propertiesMap field-value键值对
	 * @return
	 */
	@Override
	public List<T> findByProperties(Map<String, String> propertiesMap) {
		return findByProperties(propertiesMap, false);
	}


	/**
	 * @param propertiesMap field-value键值对
	 * @param isRemoved     是否包含removed=1的条件
	 * @return
	 */
	@Override
	public List<T> findByProperties(Map<String, String> propertiesMap, boolean isRemoved) {
		StringBuffer sbl = buildSql(propertiesMap, isRemoved);
		return entityManager.createQuery(sbl.toString(), entityInformation.getJavaType()).getResultList();
	}

	/**
	 * 分页根据查询键值对查询条件返回对象分页数据
	 *
	 * @param propertiesMap field-value键值对
	 * @param pageable      分页对象
	 * @return
	 */
	@Override
	public Page<T> pageByProperties(Map<String, String> propertiesMap, Pageable pageable) {
		return null;
	}

	/**
	 * 获得对象的字段，然后缓存起来，下次使用直接返回，
	 * 存在问题，如果字段进行修改，但项目没有重启，会存在字段对应不上的问题，所以再修改字段后，最好重启服务
	 * @return
	 */
	private Map<String, Field> getCachedFieldMap() {
		Class clazz = entityInformation.getJavaType();
		String className = clazz.getSimpleName();
		Map<String, Field> fieldMap = classMap.get(className);
		if (fieldMap == null) {
			fieldMap = new HashMap<>(20);
			Field[] fields;
			// BaseDomain
			if (BaseDomain.class.isAssignableFrom(clazz)) {
				fields = BaseDomain.class.getDeclaredFields();
				for (Field field : fields) {
					fieldMap.put(field.getName(), field);
				}
			}
			fields = clazz.getDeclaredFields();
			for (Field field : fields) {
				fieldMap.put(field.getName(), field);
			}
			classMap.put(className, fieldMap);
		}
		return fieldMap;
	}
}
